package com.zhl.projectmerger;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjectUtil;
import javafx.util.Pair;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 本工具类仅用于合并两个简单的java maven项目，当前只完成java源文件的增量合并
 * 对于pom文件，各种resource资源文件,现阶段没有做合并逻辑
 */
public class ProjectMergeUtil {
    /**
     * 合并两个简单的java maven项目
     * 合并逻辑：
     * 1：用新项目的所有非java文件覆盖老目的文件，对于target目的，git目录等可以在配置信息里面配置排除
     * 2. java文件增量合并，新增的java文件直接覆盖，已存在的java文件做内容比对后增量合并
     * 3. 合并完的项目是一个完整项目结构的java maven项目
     *
     * @param mergerConfig
     */
    public static void projectMerge(ProjectMergeConfig mergerConfig) {
        try {
            // 1、参数校验，对于必要的配置参数进行校验
            checkMergeConfig(mergerConfig);
            // 2、合并新项目的非java文件到目标项目
            mergeNoJavaFiles(mergerConfig);
            // 3、src/main/java 文件目录下的 java 文件处理
            mergeJavaFiles(mergerConfig);
        } catch (Exception exception) {
            exception.printStackTrace();
            // throw new RuntimeException(exception);
        }
    }

    /**
     * 校验配置文件
     *
     * @param mergerConfig
     */
    public static void checkMergeConfig(ProjectMergeConfig mergerConfig) {
        if (ObjectUtil.isEmpty(mergerConfig.getOldProjectPath())) {
            throw new RuntimeException("oldProjectPath must not be null.");
        } else if (ObjectUtil.isEmpty(mergerConfig.getNewProjectPath())) {
            throw new RuntimeException("newProjectPath must not be null.");
        } else if (ObjectUtil.isEmpty(mergerConfig.getTargetProjectPath())) {
            throw new RuntimeException("targetProjectPath must not be null.");
        } else if (StringUtils.isEmpty(mergerConfig.getProjectName())) {
            throw new RuntimeException("projectMame must not be null.");
        }

        if (mergerConfig.getNewProjectPath().equals(mergerConfig.getOldProjectPath())) {
            throw new RuntimeException("newProjectPath must not be the same as oldProjectPath.");
        }

        if (mergerConfig.isMergeToOldProject()) {
            if (!mergerConfig.getTargetProjectPath().equals(mergerConfig.getOldProjectPath())) {
                throw new RuntimeException("oldProjectPath must be the same as the targetProject.");
            }
        } else {
            if (mergerConfig.getTargetProjectPath().equals(mergerConfig.getOldProjectPath())) {
                throw new RuntimeException("oldProjectPath must not be the same as the targetProject.");
            }
            // 获取目标目录是否为空的标识
            boolean isTargetProjectEmpty;
            if (mergerConfig.getTargetProjectPath().toFile().list() == null) {
                isTargetProjectEmpty = true;
            } else {
                isTargetProjectEmpty = Objects.requireNonNull(mergerConfig.getTargetProjectPath().toFile().list()).length == 0;
            }

            // 不允许多次覆盖合并
            if (!mergerConfig.isAllowReplace()) {
                if (!isTargetProjectEmpty) {
                    throw new RuntimeException("targetProject must be empty.");
                }
            } else {
                // 允许多次覆盖合并 && 目标项目目录非空
                if (!isTargetProjectEmpty) {
                    for (String fileName : Objects.requireNonNull(mergerConfig.getTargetProjectPath().toFile().list())) {
                        if (!fileName.equals(mergerConfig.getProjectName())) {
                            throw new RuntimeException("targetProject sub dirs or files must " +
                                    "only hava projectName as sub dir name.");
                        }
                    }
                }
            }
        }

        // 重新设定配置信息,新的项目目录为之前的路径加上项目路径
        mergerConfig.setOldProjectPath(mergerConfig.getOldProjectPath().resolve(mergerConfig.getProjectName()));
        mergerConfig.setNewProjectPath(mergerConfig.getNewProjectPath().resolve(mergerConfig.getProjectName()));
        mergerConfig.setTargetProjectPath(mergerConfig.getTargetProjectPath().resolve(mergerConfig.getProjectName()));
    }

    /**
     * 合并非java文件
     * 通过覆盖的方式合并非java的文件
     *
     * @param mergerConfig
     */
    public static void mergeNoJavaFiles(ProjectMergeConfig mergerConfig) throws IOException {
        // 如果老项目和目标项目的路径一样，则不进行文件复制，目录不一样则进行复制。
        if (!mergerConfig.isMergeToOldProject()) {
            copyDirectory(
                    mergerConfig.getOldProjectPath(),
                    mergerConfig.getTargetProjectPath(),
                    mergerConfig.getExcludeDirs(),
                    mergerConfig.getExcludeFiles()
            );
        }
    }

    /**
     * 合并java文件
     *
     * @param mergeConfig
     */
    public static void mergeJavaFiles(ProjectMergeConfig mergeConfig) throws IOException {
        // 列出所有的java文件
        List<File> files = FileUtil.loopFiles(mergeConfig.getNewProjectPath(),
                pathname -> {
                    return pathname.getName().endsWith(".java");
                });

        AtomicInteger totalFiles = new AtomicInteger(0);
        for (File mergeFrom : files) {
            mergeConfig.getExecutorService().submit( () -> {
                String mergeFromPathStr = mergeFrom.getAbsolutePath();
                String mergeToPthStr = "";
                //winddows 系统路径处理
                if (System.getProperty("os.name").toLowerCase().startsWith("win")) {
                    mergeFromPathStr = mergeFromPathStr.replaceAll("\\\\", "/");
                    String newProjectPathStr = mergeConfig.getNewProjectPath().toString().replaceAll("\\\\", "/");
                    String oldProjectPathStr = mergeConfig.getTargetProjectPath().toString().replaceAll("\\\\", "/");
                    mergeToPthStr = mergeFromPathStr.replaceFirst(newProjectPathStr, oldProjectPathStr);
                } else {
                    mergeToPthStr = mergeFromPathStr.replaceFirst(mergeConfig.getNewProjectPath().toString(),
                            mergeConfig.getTargetProjectPath().toString());
                }

                File mergeTo = new File(mergeToPthStr);

                if (mergeTo.exists()) {
                    // 如果目标文件存在，则进行内容比对
                    mergeConfig.getExecutorService().submit( () -> {
                        try {
                            JavaSourceCodeMerger.getInstance().merge(mergeFrom, mergeTo, mergeConfig);
                        } catch (Exception e) {
                            mergeConfig
                                    .getMergeFailedFileNames()
                                    .add(new Pair<String, String>(mergeFrom.getAbsolutePath(), mergeTo.getAbsolutePath()));
                            e.printStackTrace();
                        }

                    });
                } else {
                    CopyOption[] options = new CopyOption[]{StandardCopyOption.COPY_ATTRIBUTES};
                    try {
                        Files.createDirectories(Paths.get(mergeToPthStr).getParent());
                        Files.copy(Paths.get(mergeFromPathStr), Paths.get(mergeToPthStr), options);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }

                }
                totalFiles.incrementAndGet();
            });
        }
        while (totalFiles.get() != files.size()) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 复制整个目录的内容到另一个目录，可以指定排除某些目录。
     *
     * @param source       源目录的Path对象
     * @param target       目标目录的Path对象
     * @param excludedDirs 要排除复制的目录集合，这些目录及其内容不会被复制
     * @throws IOException 如果发生I/O错误
     */
    private static void copyDirectory(Path source, Path target, Set<String> excludedDirs, Set<String> excludedFiles)
            throws IOException {
        if (!Files.exists(target)) {
            Files.createDirectories(target);
        }

        Files.walkFileTree(source, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                // 检查当前目录是否在排除列表中
                for (String excluded : excludedDirs) {
                    Path dirFileName = dir.getFileName();
                    Path pomPath = dir.resolve("../pom.xml");
                    if (pomPath.toFile().exists() && (dirFileName.equals(Paths.get(excluded)))) {
                        return FileVisitResult.SKIP_SUBTREE; // 跳过此目录及其内容
                    }
                }
                return super.preVisitDirectory(dir, attrs);
            }

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                // 排除的文件列表里面的文件不拷贝
                for (String excluded : excludedFiles) {
                    Path fileName = file.getFileName();
                    if (fileName.endsWith(Paths.get(excluded))) {
                        return FileVisitResult.CONTINUE;
                    }
                }

                Path targetFile = target.resolve(source.relativize(file));
                CopyOption[] options = new CopyOption[]{StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING};

                // 检查目标路径是否存在，如果不存在就创建该路径
                Files.createDirectories(targetFile.getParent());
                Files.copy(file, targetFile, options);
                return FileVisitResult.CONTINUE;
            }
        });
    }
}